// <copyright file="EcoScript.cs" company="Codeplex project Eco.Contrib"> 
// The MIT License (MIT) 
// </copyright> 
// <author>Peter Buchmann</author> 

namespace Eco.Contrib
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Eco.ObjectRepresentation;
    using Eco.Services;
    using Eco.UmlCodeAttributes;
    using Eco.UmlRt;

    /// <summary>
    /// EcoScript: Represents a EcoScript
    /// </summary>
    public partial class EcoScript
    {
        /// <summary>
        /// recognitionTokens, a dictionary of tokens to recognize
        /// </summary>
        private static Dictionary<IList<EslToken>, Type> recognitionTokens = null;

        /// <summary>
        /// Initializes a new instance of the <see cref="EcoScript"/> class.
        /// </summary>
        /// <param name="serviceProvider">The service provider.</param>
        /// <param name="name">The name.</param>
        /// <param name="script">The script.</param>
        /// <param name="parameters">The parameters.</param>
        [UmlElement(Id = "43acc725-f6da-4dc9-9abf-0fa0b343140b")]
        public EcoScript(IEcoServiceProvider serviceProvider, string name, string script, string parameters)
            : this(serviceProvider)
        {
            if (recognitionTokens == null)
            {
                recognitionTokens = new Dictionary<IList<EslToken>, Type>();
                this.RegisterInstructionRecognition(typeof(EslInstruction));
            }

            this.Name = name;
            this.Text = script;
            this.Parameters = parameters;
        }

        /// <summary>
        /// Executes script with the specified service provider.
        /// </summary>
        /// <param name="serviceProvider">The service provider.</param>
        /// <returns>Return value of executed function</returns>
        [UmlElement(Id = "e1eeca7e-349b-426c-bb7f-3188499c0754")]
        public IElement Execute(IEcoServiceProvider serviceProvider)
        {
            if (serviceProvider == null)
            {
                throw new EslException("Parameter is null");
            }

            IModifiableVariableList variableList = serviceProvider.GetEcoService<IVariableFactoryService>().CreateVariableList();
            return Execute(serviceProvider, variableList);
        }

        /// <summary>
        /// Executes script with the specified service provider.
        /// </summary>
        /// <param name="serviceProvider">The service provider.</param>
        /// <param name="variableList">The variable list.</param>
        /// <returns>Return value of executed function</returns>
        [UmlElement(Id = "6334a321-1770-4024-a714-de5b9cea406b")]
        public IElement Execute(IEcoServiceProvider serviceProvider, IModifiableVariableList variableList)
        {
            if (this.FirstInstruction != null)
            {
                IElement element = this.FirstInstruction.Execute(serviceProvider, variableList);
                if ((element != null) && (element.AsObject is ReturnElement))
                {
                    IElement returnElement = (element.AsObject as ReturnElement).Element;
                    (element as IObject).Delete();
                    return returnElement;
                }
                else
                {
                    return element;
                }
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Parses this instance.
        /// </summary>
        [UmlElement(Id = "a3e6f8af-c5a5-451a-bf38-6048a460820e")]
        public void Parse()
        {
            EslTokenReader tokenReader = new EslTokenReader(this.Text);
            List<EslToken> programTokenList = new List<EslToken>();
            EslToken token;
            do
            {
                token = tokenReader.GetNextToken();
                if (token.TokenType != EslTokenType.Comment)
                {
                    programTokenList.Add(token);
                }
            }
            while (token.TokenType != EslTokenType.End);

            EslInstruction lastInstruction = null;
            EslInstruction instruction = null;
            do
            {
                instruction = this.ReadNextInstruction(programTokenList, true);
                if (instruction != null)
                {
                    if (lastInstruction == null)
                    {
                        this.FirstInstruction = instruction;
                    }
                    else
                    {
                        lastInstruction.NextInstruction = instruction;
                    }

                    lastInstruction = instruction;
                }
            }
            while (instruction != null);
        }

        /// <summary>
        /// Gets the text between tokens.
        /// </summary>
        /// <param name="firstToken">The first token.</param>
        /// <param name="secondToken">The second token.</param>
        /// <returns>Text between tokens</returns>
        [UmlElement(Id = "e68d34d1-6290-43eb-9d56-061f558491a0")]
        public string GetTextBetweenTokens(EslToken firstToken, EslToken secondToken)
        {
            if (firstToken == null || secondToken == null)
            {
                throw new EslException("Parameter is null");
            }

            return this.GetText(firstToken.Pos - 1, secondToken.Pos - firstToken.Pos);
        }

        /// <summary>
        /// Gets the text.
        /// </summary>
        /// <param name="position">The position.</param>
        /// <param name="length">The length.</param>
        /// <returns>Text from position with given length</returns>
        [UmlElement(Id = "1aa70644-8f0b-456d-8eb5-b0cc89a1c711")]
        public string GetText(int position, int length)
        {
            if (position < 0)
            {
                position = 0;
            }

            if (position >= this.Text.Length)
            {
                position = this.Text.Length - 1;
            }

            if (position + length >= this.Text.Length)
            {
                length = this.Text.Length - position;
            }

            return this.Text.Substring(position, length);
        }

        /// <summary>
        /// Adds the parameter.
        /// </summary>
        /// <param name="variableList">The variable list.</param>
        /// <param name="element">The element.</param>
        [UmlElement(Id = "9507dc8b-ef9d-4ab1-8054-3dadfd937233")]
        public void AddParameter(IModifiableVariableList variableList, IElement element)
        {
            if (variableList == null)
            {
                throw new EslException("Parameter is null");
            }

            string parameter = this.Parameters.Split(',')[variableList.Count].Trim();
            IExternalVariable variable = variableList[parameter];
            if (variable != null)
            {
                variableList.Remove(parameter);
            }

            variableList.Add(parameter, element);
        }

        /// <summary>
        /// Reads the next instruction.
        /// </summary>
        /// <param name="programTokenList">The program token list.</param>
        /// <param name="fullExpression">if set to <c>true</c> [full expression].</param>
        /// <returns>Next instruction</returns>
        [UmlElement(Id = "14635065-09ee-4bf5-8377-f5081d9c50eb")]
        public EslInstruction ReadNextInstruction(IList<EslToken> programTokenList, bool fullExpression)
        {
            EslInstruction instruction = null;

            List<EslToken> tokenList = new List<EslToken>();

            int pos = 0;
            while ((pos < programTokenList.Count) || (instruction != null))
            {
                if (programTokenList[pos].TokenType == EslTokenType.End)
                {
                    if (tokenList.Count == 0)
                    {
                        break;
                    }

                    throw new EslException("Was not able to parse " + tokenList[0].ToString() + " and following tokens.", tokenList[0]);
                }

                tokenList.Add(programTokenList[pos]);
                Type instructionType = IdentifyInstruction(tokenList);
                if (instructionType == null)
                {
                    pos++;
                }
                else
                {
                    IObjectInstance objekt = this.ServiceProvider().GetEcoService<IObjectFactoryService>().CreateNewObject(instructionType);
                    instruction = objekt.AsObject as EslInstruction;
                    int programTokenListCount = programTokenList.Count;
                    instruction.Parse(programTokenList, this, fullExpression);

                    if (programTokenListCount == programTokenList.Count)
                    {
                        throw new EslException("Method Parse of class " + instructionType.ToString() + " did not reduce token list.");
                    }

                    break;
                }
            }

            return instruction;
        }

        /// <summary>
        /// Identifies the instruction.
        /// </summary>
        /// <param name="tokenList">The token list.</param>
        /// <returns>Identified type of instruction</returns>
        [UmlElement(Id = "e1257071-4f4c-4f00-9219-9ef9803f2a5e")]
        public static Type IdentifyInstruction(IList<EslToken> tokenList)
        {
            Dictionary<Type, int> typeList = new Dictionary<Type, int>();
            Type elseType = null;
            foreach (KeyValuePair<IList<EslToken>, Type> recognitionToken in recognitionTokens)
            {
                if (recognitionToken.Key.Count == 0)
                {
                    elseType = recognitionToken.Value;
                }
                else
                {
                    bool found = true;
                    int i;
                    for (i = 0; i < Math.Min(recognitionToken.Key.Count, tokenList.Count); i++)
                    {
                        if (recognitionToken.Key[i].TokenType != tokenList[i].TokenType)
                        {
                            found = false;
                            break;
                        }

                        if ((recognitionToken.Key[i].TokenValue != "%") &&
                            (recognitionToken.Key[i].TokenValue.ToLower() != tokenList[i].TokenValue.ToLower()))
                        {
                            found = false;
                            break;
                        }
                    }

                    if (found)
                    {
                        if (typeList.ContainsKey(recognitionToken.Value))
                        {
                            if (i == recognitionToken.Key.Count)
                            {
                                typeList.Remove(recognitionToken.Value);
                                typeList.Add(recognitionToken.Value, recognitionToken.Key.Count);
                            }
                        }
                        else
                        {
                            typeList.Add(recognitionToken.Value, (i == recognitionToken.Key.Count) ? recognitionToken.Key.Count : -1);
                        }
                    }
                }
            }

            bool allMatchedCompletely = true;
            int maxTokens = 0;
            Type returnValue = null;
            foreach (KeyValuePair<Type, int> type in typeList)
            {
                if (type.Value > 0)
                {
                    if (maxTokens < type.Value)
                    {
                        maxTokens = type.Value;
                        returnValue = type.Key;
                    }
                    else if (maxTokens < type.Value)
                    {
                        throw new EslException("Two identical instruction patterns found.");
                    }
                }
                else
                {
                    allMatchedCompletely = false;
                }
            }

            if ((typeList.Count > 1) && allMatchedCompletely)
            {
                return returnValue;
            }
            else if (typeList.Count == 1)
            {
                return typeList.First<KeyValuePair<Type, int>>().Key;
            }
            else if ((typeList.Count == 0) && (elseType != null))
            {
                return elseType;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Registers the instruction recognition.
        /// </summary>
        /// <param name="type">The type.</param>
        [UmlElement(Id = "2dd23224-6863-43c7-b034-0aad9d011796")]
        private void RegisterInstructionRecognition(Type type)
        {
            foreach (IClass classLoop in this.ServiceProvider().GetEcoService<ITypeSystemService>().TypeSystem.GetClassByType(type).SubTypes)
            {
                if (!classLoop.IsAbstract)
                {
                    EslInstruction instruction = this.ServiceProvider().GetEcoService<IObjectFactoryService>().CreateNewObject(classLoop).AsObject as EslInstruction;
                    instruction.Init(recognitionTokens);
                    instruction.AsIObject().Delete();
                }

                this.RegisterInstructionRecognition(classLoop.ObjectType);
            }
        }
    }
}